﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

namespace PluginInstaller
{
    public class AppConfigHelper
    {
        private readonly string _path;
        private const string NsMsAsmV1 = "urn:schemas-microsoft-com:asm.v1";

        private bool _changed;
        private readonly XDocument _doc;
        private XElement _elConfiguration;
        private XElement _elRuntime;
        private XElement _elAssemblyBinding;
        private XmlNamespaceManager _nsManager;
        private XNamespace _nsMsAsmV1;

        private void Init()
        {
            _elConfiguration = _doc.XPathSelectElement("/configuration");
            _elRuntime = _elConfiguration.XPathSelectElement("runtime");
            if (_elRuntime == null) {
                _elRuntime = new XElement("runtime");
                _elConfiguration.Add(_elRuntime);
            }

            _nsMsAsmV1 = NsMsAsmV1;
            _nsManager = new XmlNamespaceManager(new NameTable());
            _nsManager.AddNamespace("ms", _nsMsAsmV1.NamespaceName);

            _elAssemblyBinding = _elRuntime.XPathSelectElement("ms:assemblyBinding", _nsManager);
            if (_elAssemblyBinding == null) {
                _elAssemblyBinding = new XElement(_nsMsAsmV1 + "assemblyBinding");
                _elRuntime.Add(_elAssemblyBinding);
            }
        }

        public AppConfigHelper(string path)
        {
            _path = path;
            _doc = XDocument.Load(path);
            _doc.Changed += (sender, args) => _changed = true;
            Init();
        }

        public void SetPrivatePath(string path)
        {
            var element = _elAssemblyBinding.XPathSelectElement(@"ms:probing[@privatePath]", _nsManager);
            if (element == null) {
                element = new XElement(_nsMsAsmV1 + "probing");
                element.SetAttributeValue("privatePath", path);
                _elAssemblyBinding.Add(element);
            }
            else {
                var attribute = element.Attribute("privatePath");
                if (attribute == null) return;
                var item = attribute.Value.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                if (!new HashSet<string>(item).Contains(path)) {
                    attribute.Value = attribute.Value + ";" + path;
                }
            }
        }

        public void SetDependentAssembly(string name, string publicKeyToken, string oldVersion, string newVersion)
        {
            var list = _elAssemblyBinding.XPathSelectElements("ms:dependentAssembly", _nsManager);
            foreach (var element in list) {
                var elId = element.XPathSelectElement("ms:assemblyIdentity", _nsManager);
                var attribute = elId?.Attribute("name");
                if (attribute == null || attribute.Value != name) {
                    continue;
                }
                element.Remove();
                break;
            }
            var elDepAsm = new XElement(_nsMsAsmV1 + "dependentAssembly");
            var elAsmId = new XElement(_nsMsAsmV1 + "assemblyIdentity");
            elAsmId.SetAttributeValue("name", name);
            elAsmId.SetAttributeValue("publicKeyToken", publicKeyToken);
            elAsmId.SetAttributeValue("culture", "neutral");
            elDepAsm.Add(elAsmId);
            var elBinding = new XElement(_nsMsAsmV1 + "bindingRedirect");
            elBinding.SetAttributeValue("oldVersion", "0.0.0.0-" + oldVersion);
            elBinding.SetAttributeValue("newVersion", newVersion);
            elDepAsm.Add(elBinding);
            _elAssemblyBinding.Add(elDepAsm);
        }

        public void Save()
        {
            if (_changed) {
                _doc.Save(_path);
            }
        }
    }
}